/*-------------------------------------------------------------------------
 *
 * be-secure-common.c
 *
 * 实现无关的SSL支持代码
 *
 * while be-secure.c包含其他通信代码调用的接口时，
 * 本文件包含库特定实现（例如be-secure-openssl.c）使用的支持例程。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	  src/backend/libpq/be-secure-common.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include <sys/stat.h>
#include <unistd.h>

#include "common/string.h"
#include "libpq/libpq.h"
#include "storage/fd.h"

/*
 * 运行 ssl_passphrase_command
 *
 * 提示将替换为 %p。is_server_start 决定错误消息的日志级别。
 *
 * 结果将被放入大小为 size 的缓冲区 buf 中。返回值是实际结果的长度。
 */
int
#ifndef FDDGM
run_ssl_passphrase_command(const char *fc_prompt, bool fc_is_server_start, char *fc_buf, int fc_size)
#else
run_ssl_passphrase_command(const char *fc_prompt, bool fc_is_server_start, char *fc_buf, int fc_size,bool fc_is_enc_key)
#endif
{
	int			fc_loglevel = fc_is_server_start ? ERROR : LOG;
	StringInfoData fc_command;
	char	   *fc_p;
	FILE	   *fc_fh;
	int			fc_pclose_rc;
	size_t		fc_len = 0;

	Assert(fc_prompt);
	Assert(fc_size > 0);
	fc_buf[0] = '\0';

	initStringInfo(&fc_command);

#ifndef FDDGM
	for (fc_p = ssl_passphrase_command; *fc_p; fc_p++)
#else
	for (fc_p = fc_is_enc_key ? ssl_encpassphrase_command : ssl_passphrase_command; *fc_p; fc_p++)
#endif
	{
		if (fc_p[0] == '%')
		{
			switch (fc_p[1])
			{
				case 'p':
					appendStringInfoString(&fc_command, fc_prompt);
					fc_p++;
					break;
				case '%':
					appendStringInfoChar(&fc_command, '%');
					fc_p++;
					break;
				default:
					appendStringInfoChar(&fc_command, fc_p[0]);
			}
		}
		else
			appendStringInfoChar(&fc_command, fc_p[0]);
	}

	fc_fh = OpenPipeStream(fc_command.data, "r");
	if (fc_fh == NULL)
	{
		ereport(fc_loglevel,
				(errcode_for_file_access(),
				 errmsg("could not execute command \"%s\": %m",
						fc_command.data)));
		goto error;
	}

	if (!fgets(fc_buf, fc_size, fc_fh))
	{
		if (ferror(fc_fh))
		{
			explicit_bzero(fc_buf, fc_size);
			ereport(fc_loglevel,
					(errcode_for_file_access(),
					 errmsg("could not read from command \"%s\": %m",
							fc_command.data)));
			goto error;
		}
	}

	fc_pclose_rc = ClosePipeStream(fc_fh);
	if (fc_pclose_rc == -1)
	{
		explicit_bzero(fc_buf, fc_size);
		ereport(fc_loglevel,
				(errcode_for_file_access(),
				 errmsg("could not close pipe to external command: %m")));
		goto error;
	}
	else if (fc_pclose_rc != 0)
	{
		explicit_bzero(fc_buf, fc_size);
		ereport(fc_loglevel,
				(errcode_for_file_access(),
				 errmsg("command \"%s\" failed",
						fc_command.data),
				 errdetail_internal("%s", wait_result_to_str(fc_pclose_rc))));
		goto error;
	}

	/* 去掉尾随的换行符和回车 */
	fc_len = pg_strip_crlf(fc_buf);

error:
	pfree(fc_command.data);
	return fc_len;
}


/*
 * 检查 SSL 密钥文件的权限。
 */
bool check_ssl_key_file_permissions(const char *fc_ssl_key_file, bool fc_isServerStart)
{
	int			fc_loglevel = fc_isServerStart ? FATAL : LOG;
	struct stat fc_buf;

	if (stat(fc_ssl_key_file, &fc_buf) != 0)
	{
		ereport(fc_loglevel,
				(errcode_for_file_access(),
				 errmsg("could not access private key file \"%s\": %m",
						fc_ssl_key_file)));
		return false;
	}

	/* 密钥文件必须是常规文件 */
	if (!S_ISREG(fc_buf.st_mode))
	{
		ereport(fc_loglevel,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("private key file \"%s\" is not a regular file",
						fc_ssl_key_file)));
		return false;
	}

	/*
	 * 拒绝加载由我们或 root 以外的用户拥有的密钥文件，并
	 * 要求密钥文件没有公共访问权限。如果文件由我们拥有，
	 * 要求模式为 0600 或更低。如果由 root 拥有，则要求 0640 或更低，
	 * 以允许通过我们的 gid 或允许我们读取系统范围证书的附加 gid 进行读取。
	 *
	 * 请注意，在 src/interfaces/libpq/fe-secure-openssl.c 中执行了大致类似的检查，
	 * 因此这里的任何更改可能也需要在那里进行。环境是不同的；这
	 * 段代码可以假设我们不是以 root 身份运行。
	 *
	 * 理想情况下，我们会在 Windows 上进行类似的权限检查，但并不清楚这将如何运作，
	 * 因为 Unix 风格的权限可能不可用。
	 */
#if !defined(WIN32) && !defined(__CYGWIN__)
	if (fc_buf.st_uid != geteuid() && fc_buf.st_uid != 0)
	{
		ereport(fc_loglevel,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("private key file \"%s\" must be owned by the database user or root",
						fc_ssl_key_file)));
		return false;
	}

	if ((fc_buf.st_uid == geteuid() && fc_buf.st_mode & (S_IRWXG | S_IRWXO)) ||
		(fc_buf.st_uid == 0 && fc_buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO)))
	{
		ereport(fc_loglevel,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("private key file \"%s\" has group or world access",
						fc_ssl_key_file),
				 errdetail("File must have permissions u=rw (0600) or less if owned by the database user, or permissions u=rw,g=r (0640) or less if owned by root.")));
		return false;
	}
#endif

	return true;
}
